﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BGSpaceShooter.View;
using BGSpaceShooterServer;
using Microsoft.Xna.Framework;

namespace BGSpaceShooter
{
    public class GameObjectContainer : IStatusObserver, IStateObserver
    {
        private Random random = new Random();
        private const int MaxObjects = 1000;
        private DateTime lastUpdate;
        private GameTime GameTime { get; set; }

        public GameObjectContainer()
        {
            GameTime = new GameTime();
            StarFighters = new List<StarFighter>();
            Projectiles = new Projectile[MaxObjects];
            ZombieProjectiles = new List<ZombieProjectile>();

            GameObjectFactory = new GameObjectFactory();
            Player = -1;
            lastUpdate = DateTime.Now;
        }

        public int Player { get; set; }

        public StarFighter PlayerStarFighter
        {
            get
            {
                if (Player >= 0)
                {
                    if (Player < StarFighters.Count)
                    {
                        return StarFighters[Player];
                    }
                    throw new Exception("Game not yet started");
                }
                throw new Exception("Player not set!");
            }
        }

        private List<StarFighter> StarFighters
        {
            get;
            set;
        }

        public StarFighter[] Fighters
        {
            get
            {
                if (StarFighters == null)
                {
                    throw new NullReferenceException("Not yet initialized");
                }
                return StarFighters.ToArray();
            }
        }

        public Projectile[] Projectiles
        {
            get;
            set;
        }

        private List<ZombieProjectile> ZombieProjectiles { get; set; }

        protected GameObjectFactory GameObjectFactory
        {
            get;
            set;
        }

        #region IStatusObserver Members

        public void Update(ServerStatusResponse statusResponse)
        {
            if (StarFighters[statusResponse.playerId] != null && statusResponse.TimeStamp > StarFighters[statusResponse.playerId].LastUpdate)
            {
                StarFighters[statusResponse.playerId].Position = statusResponse.Position;
                StarFighters[statusResponse.playerId].RotationMatrix = statusResponse.Rotation;
                StarFighters[statusResponse.playerId].LastUpdate = statusResponse.TimeStamp;
                StarFighters[statusResponse.playerId].WorkingEngines = statusResponse.Engines;
                StarFighters[statusResponse.playerId].Velocity = statusResponse.Velocity;
            }
        }

        public void BeginUpdate()
        {
            DateTime now = DateTime.Now;
            UpdateProjectiles(now - lastUpdate);

        }

        public void EndUpdate()
        {
            UpdateEffects(new GameTime(DateTime.Now - lastUpdate, DateTime.Now - lastUpdate));
            lastUpdate = DateTime.Now;
        }

        #endregion

        #region IStateObserver Members

        public void Update(ServerStateResponse response)
        {
            if (response.EventType == EventType.Creation)
            {
                if (response.ObjectName == GameObjectName.Asteroid)
                    CreateAsteroid(response.ObjectId, response.Position);
                if (response.ObjectName == GameObjectName.Bullet)
                    CreateBullet(response.ObjectId, response.Position, response.Rotation, response.Velocity);
            }
            else if (response.EventType == EventType.Destruction)
            {
                if (response.ObjectType == GameObjectType.Projectile)
                    DestroyProjectile(response.ObjectId);
                else
                {
                    StarFighters[response.ObjectId] = null;
                }
            }
            else if (response.EventType == EventType.HPUpdate)
            {
                StarFighter fighter = StarFighters[response.ObjectId];
                if (response.ComponentIndex == -1)
                {
                    if (DateTime.Compare(fighter.LastHPUpdate, DateTime.Now) < 0)
                    {
                        fighter.HP = response.HPValue;
                    }
                }
                else
                {
                    if (DateTime.Compare(fighter.StarFighterComponents[response.ComponentIndex].LastHPUpdate, DateTime.Now) < 0)
                    {
                        fighter.StarFighterComponents[response.ComponentIndex].HP = response.HPValue;
                    }
                }
            }
        }

        #endregion

        public StarFighter CreateStarFighter(int id, Vector3 position, Matrix rotation)
        {
            StarFighter fighter = GameObjectFactory.CreateStarFighter();
            fighter.Position = position;
            fighter.RotationMatrix = rotation;
            //robimy miejsce pod graczy o ktorych jeszcze nie mamy info z serwera
            if (StarFighters.Count <= id)
            {
                while (StarFighters.Count < id)
                    StarFighters.Add(null);
                StarFighters.Add(fighter);
            }
            else
                StarFighters[id] = fighter;

            return fighter;
        }

        /// <summary>
        /// Tworzy asteroide. 
        /// </summary>
        /// <remarks>UWAGA: Tworzy losowa rotacje (nie musi byc przesylana bo i tak nie wplywa na kolizje)</remarks>
        /// <param name="position"></param>
        /// <returns></returns>
        private Projectile CreateAsteroid(int id, Vector3 position)
        {
            if (id > MaxObjects)
                throw new Exception("Niepoprawny ID obiektu!");
            Projectile asteroid = GameObjectFactory.CreateProjectile(GameObjectName.Asteroid);
            asteroid.Position = position;
            asteroid.RotationMatrix = Matrix.CreateRotationX((float)random.NextDouble()) * Matrix.CreateRotationY((float)random.NextDouble()) * Matrix.CreateRotationZ((float)random.NextDouble());
            Projectiles[id] = asteroid;
            return asteroid;
        }

        private Projectile CreateBullet(int id, Vector3 position, Matrix rotation, Vector3 velocity)
        {
            if (id > MaxObjects)
                throw new Exception("Niepoprawny ID obiektu!");
            Projectile bullet = GameObjectFactory.CreateProjectile(GameObjectName.Bullet);
            bullet.Position = new Vector3(position.X, position.Y, position.Z);
            bullet.Velocity = new Vector3(velocity.X, velocity.Y, velocity.Z);
            bullet.RotationMatrix = new Matrix(rotation.M11, rotation.M12, rotation.M13, rotation.M14, rotation.M21,
                                               rotation.M22, rotation.M23, rotation.M24, rotation.M31, rotation.M32,
                                               rotation.M33, rotation.M34, rotation.M41, rotation.M42, rotation.M43,
                                               rotation.M44);
            bullet.MotionEffect.IsActive = true;
            Projectiles[id] = bullet;

            return bullet;
        }

        private void DestroyProjectile(int id)
        {
            if (Projectiles[id] != null)
                ZombieProjectiles.Add(new ZombieProjectile(Projectiles[id]));
            Projectiles[id] = null;
        }

        public List<IDrawer> GetObjectDrawers()
        {
            lock (this)
            {
                List<IDrawer> drawers = new List<IDrawer>();
                foreach (StarFighter starFighter in StarFighters)
                {
                    if (starFighter != null)
                    {
                        drawers.Add(starFighter.GetDrawer());
                    }
                }

                foreach (Projectile projectile in Projectiles)
                {
                    if (projectile != null)
                        drawers.Add(projectile.GetDrawer());
                }

                drawers.Add(new ZombieDrawer(ZombieProjectiles));

                return drawers;
            }
        }

        public List<IDrawer> GetBillboardDrawers()
        {
            lock (this)
            {
                List<IDrawer> drawers = new List<IDrawer>();
                int i = 0;
                foreach (StarFighter starFighter in StarFighters.Where(s => s != PlayerStarFighter))
                {
                    if(i == Player)
                    {
                        ++i;
                    }
                    if (starFighter != null)
                    {
                        drawers.Add(new BillboardDrawer(starFighter, PlayerStarFighter, "Player " + i));
                    }
                    ++i;
                }

                return drawers;
            }
        }

        private void UpdateProjectiles(TimeSpan time)
        {
            foreach (Projectile projectile in Projectiles)
            {
                if (projectile != null)
                    projectile.UpdatePosition(time);
            }

            for (int i = ZombieProjectiles.Count - 1; i >= 0; i--)
            {
                if (ZombieProjectiles[i].checkIfOutdated())
                    ZombieProjectiles.RemoveAt(i);
            }
        }


        private void UpdateEffects(GameTime gt)
        {

            foreach (var zombieProjectile in ZombieProjectiles)
            {
                foreach (var objectEffect in zombieProjectile.getObjectEffects())
                {
                    objectEffect.Update(gt);
                }
            }

            foreach (var projectile in Projectiles)
            {
                if (projectile != null)
                    foreach (var objectEffect in projectile.GetEffectsToDraw())
                    {
                        objectEffect.Update(gt);
                    }
            }

            foreach (StarFighter starFighter in StarFighters)
            {
                if (starFighter != null)
                {
                    foreach (ParticleSystem.IObjectEffect effect in starFighter.GetEffectsToDraw())
                    {
                        effect.Update(gt);
                    }
                }
            }
        }

    }
}
